package Generic;

import java.util.ArrayList;
import java.util.List;

public class SelectionPolicy {
	public static Population fitnessProportionate(Population adults, int newPopulationSize)
	{
		List<Double> cumulativeSums = createCumulativeSums(adults,new Identity());
		Population newPopulation= generateByRoulette(adults,cumulativeSums,newPopulationSize);
		return newPopulation;
	}
	
	public static Population sigmaScaling(Population adultPopulation, int newPopulationSize)
	{	
		double sum = sumElements(adultPopulation,new Identity());
		double squareSum = sumElements(adultPopulation,new Square());
		double mean = sum/adultPopulation.getSize();
		double variance = squareSum/adultPopulation.getSize() - mean*mean;
		double sigma = Math.sqrt(variance);
		List<Double> cumulativeSums = createCumulativeSums(adultPopulation,new SigmaScale(mean,sigma));
		Population newPopulation = generateByRoulette(adultPopulation,cumulativeSums,newPopulationSize);
		return newPopulation;
	}
	
	public static Population boltzmannScaling(Population adultPopulation, int newPopulationSize, int generation)
	{	
		List<Double> cumulativeSums = createCumulativeSums(adultPopulation,new Boltzmann(generation));
		Population newPopulation = generateByRoulette(adultPopulation,cumulativeSums,newPopulationSize);
		return newPopulation;
	}
	
	public static Population tournamentSelection(Population adultPopulation, int newPopulationSize, int tournamentSize, double epsilon)
	{	
		Population newPopulation = adultPopulation.createChildPopulation();
		
		for(int i =0; i<newPopulationSize; i++)
		{
			List<Individual> tournament = getRandomIndividuals(adultPopulation,tournamentSize);
			
			assert tournament.size()==tournamentSize;
			
			if(Math.random()<epsilon)
			{
				newPopulation.addIndividual(new Individual(tournament.get((int)(Math.random()*tournament.size()))));
			}
			else
			{
				newPopulation.addIndividual(new Individual(java.util.Collections.max(tournament)));
			}
		}
		
		return newPopulation;
	}

	private static List<Individual> getRandomIndividuals(Population population,int amount) {
		List<Individual> ret = new ArrayList<Individual>();
		
		while(ret.size()<amount)
		{
			int randomNumber = (int)(Math.random()*population.getSize());

			Individual randomIndividual = population.getIndividual(randomNumber);
			
			if(ret.contains(randomIndividual))
				continue;
			else
				ret.add(randomIndividual);
		}
		return ret;
	}

	private static Population generateByRoulette(
			Population adults, List<Double> cumulativeSums,
			int newPopulationSize) {
		Population newPopulation = adults.createChildPopulation();
		
		double totalSum = cumulativeSums.get(cumulativeSums.size()-1);
		
		for (int i = 0; i < newPopulationSize; i++) 
		{
			double random = Math.random()*totalSum;
			
			int index=0;
			while(cumulativeSums.get(index)<random)
				index++;
			Individual newIndividual = new Individual(adults.getIndividual(index));
			newPopulation.addIndividual(newIndividual);
		}
		return newPopulation;
	}

	private static List<Double> createCumulativeSums(Population adults, FitnessTransform transform) {
		
		List<Double> cumulativeFitness = new ArrayList<Double>();
		double cumSum = transform.transformFitness(adults.getIndividual(0).getFitness());
		cumulativeFitness.add(cumSum);
		
		for (int i = 1; i < adults.getSize(); i++)
		{
			cumSum+=transform.transformFitness(adults.getIndividual(i).getFitness());
			cumulativeFitness.add(cumSum);
		}
		return cumulativeFitness;
	}

	private static double sumElements(Population adultPopulation, FitnessTransform transform) {
		double sum = 0;
		for (int i = 0; i < adultPopulation.getSize(); i++) {
			sum += transform.transformFitness(adultPopulation.getIndividual(i).getFitness());
		}
		return sum;
	}

	private static interface FitnessTransform
	{
		double transformFitness(double fitness);
	}
	
	private static class Identity implements FitnessTransform
	{
		public double transformFitness(double fitness) {
			return fitness;
		}
	}
	
	private static class Square implements FitnessTransform
	{
		public double transformFitness(double fitness) {
			return fitness*fitness;
		}
	}
	
	private static class SigmaScale implements FitnessTransform
	{
		private double average;
		private double sigma;
		
		SigmaScale(double average,double sigma)
		{
			this.average=average;
			this.sigma=sigma;
		}
		public double transformFitness(double fitness) {
			if(sigma==0)
				return 0;
			;
			return 1+(fitness-average)/(2*sigma);
		}
	}
	
	private static class Boltzmann implements FitnessTransform
	{
		private int generation;
		
		Boltzmann(int generation)
		{
			this.generation=generation;
		}
		
		public double transformFitness(double fitness) {
			return Math.exp(fitness*Math.sqrt(generation+1));
		}
	}
	
}
